package com.donger.mes.system.mes.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.donger.mes.system.base.entity.MesBaseBom;
import com.donger.mes.system.base.entity.MesBaseLine;
import com.donger.mes.system.base.entity.MesBaseMaterial;
import com.donger.mes.system.base.entity.MesBaseWorkCenter;
import com.donger.mes.system.base.service.MesBaseBomService;
import com.donger.mes.system.base.service.MesBaseLineService;
import com.donger.mes.system.base.service.MesBaseMaterialService;
import com.donger.mes.system.base.service.MesBaseWorkCenterService;
import com.donger.mes.system.mes.dto.MatCheckVO;
import com.donger.mes.system.mes.dto.PlanDayGanttVO;
import com.donger.mes.system.mes.entity.MesProducePlanDay;
import com.donger.mes.system.mes.entity.WorkCenterPlan;
import com.donger.mes.system.mes.enums.MesCodeEnums;
import com.donger.mes.system.mes.enums.PlanStatusEnum;
import com.donger.mes.system.mes.mapper.MesProducePlanDayMapper;
import com.donger.mes.system.mes.dto.PlanDayFormDTO;
import com.donger.mes.system.mes.service.MesProducePlanDayService;
import com.donger.mes.system.mes.service.WorkCenterPlanService;
import com.donger.common.core.utils.BizException;
import com.donger.mes.system.side.dto.MaterialReportVO;
import com.donger.mes.system.side.entity.MesSideWarehouse;
import com.donger.mes.system.side.service.MesSideWarehouseService;
import com.donger.mes.utils.mes.MesUtils;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
public class MesProducePlanDayServiceImpl extends ServiceImpl<MesProducePlanDayMapper, MesProducePlanDay> implements MesProducePlanDayService {


    private final MesBaseWorkCenterService mesBaseWorkCenterService;
    private final WorkCenterPlanService workCenterPlanService;
    private final MesBaseLineService mesBaseLineService;

    private final MesBaseMaterialService mesBaseMaterialService;

    private final MesBaseBomService mesBaseBomService;

    private final MesSideWarehouseService mesSideWarehouseService;

    /**
     * 校验日工单是否正确
     * 查询对应的产线信息
     * 根据产线信息查询对应的工作中心
     * 生成对应的工作中心数据
     */

    /**
     * 发布日工单
     * @param planCode
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void release(String planCode) {

        // 查询工单  等待中的工单
        MesProducePlanDay planDay = Optional.ofNullable(this.getOne(Wrappers.<MesProducePlanDay>lambdaQuery()
                        .eq(MesProducePlanDay::getPlanCode, planCode)
                , false)).orElseThrow(() -> new BizException("日工单不存在"));
        // 校验日工单数据是否正确
        if(!planDay.getStatusCode().equals(PlanStatusEnum.WAITING.getType())){
            throw new BizException("工单状态不为等待中不能发布工单");
        }

        // TODO 状态信息   时间信息  产线信息


        // TODO 查询产线  是否需要判断产线的运行状态




        // 查询工作中心
        List<MesBaseWorkCenter> workCenterList = Optional.ofNullable(mesBaseWorkCenterService.getListByLineCode(planDay.getLinesCode()))
                .orElseThrow(() -> new BizException("产线对应工作中心为空"));

        List<WorkCenterPlan> workCenterPlanList = workCenterList.stream().map(item -> {
            WorkCenterPlan workCenterPlan = new WorkCenterPlan();
            workCenterPlan.setWorkCenterPlanCode(MesUtils.getMesCode(MesCodeEnums.WORKCENTERPLANCODE));
            workCenterPlan.setPlanCode(planDay.getPlanCode());
            workCenterPlan.setWorkCenterCode(item.getCode());
            workCenterPlan.setName(item.getName());
            workCenterPlan.setDeviceCode(item.getDeviceCode());
            workCenterPlan.setDeviceName(item.getDeviceName());
            workCenterPlan.setPlanStartDate(planDay.getPlanStartDate());
            workCenterPlan.setPlanEndDate(planDay.getPlanEndDate());
            workCenterPlan.setProductCode(planDay.getProductCode());
            workCenterPlan.setProductName(planDay.getProductName());
            workCenterPlan.setProductCount(planDay.getProductCount());
            workCenterPlan.setProductSpeed(planDay.getProductSpeed());
            workCenterPlan.setStation(item.getStationCode());
            workCenterPlan.setStationName(item.getStationName());
            workCenterPlan.setMode(item.getMode());
            workCenterPlan.setStatusCode(PlanStatusEnum.WAITING.getType());
            workCenterPlan.setStatus(PlanStatusEnum.WAITING.getDescription());
            workCenterPlan.setBanci(workCenterPlan.getBanci());

            return workCenterPlan;
        }).collect(Collectors.toList());
        // 生产工作中心数据
        workCenterPlanService.saveBatch(workCenterPlanList);

        // 修改当前工单的数据 工单为进行中
        planDay.setStatus(PlanStatusEnum.GOING.getDescription());
        planDay.setStatusCode(PlanStatusEnum.GOING.getType());
        this.updateById(planDay);
        // 抛出对应的事件

    }

    /**
     * 新增日计划
     * 校验产线是否能用，是否冲突
     * 产品选择对应的产线是否正确
     * 选择的时间是否冲突
     *
     * @param planDayFormDTO
     */
    @Override
    @Transactional
    public void savePlanDay(@Valid PlanDayFormDTO planDayFormDTO) {

        MesBaseLine mesBaseLine = Optional.ofNullable(mesBaseLineService.infoByCode(planDayFormDTO.getLinesCode())).orElseThrow(() -> new BizException("产线中不存在该产线"));


        MesBaseMaterial mesBaseMaterial = Optional.ofNullable(mesBaseMaterialService.getOne(Wrappers.<MesBaseMaterial>lambdaQuery()
                .eq(MesBaseMaterial::getMatCode, planDayFormDTO.getProductCode()), false)).orElseThrow(() -> new BizException("物料中不出在该物料数据"));

        MesProducePlanDay planDay = new MesProducePlanDay();
        planDay.setProductName(mesBaseMaterial.getMatName());
        BeanUtil.copyProperties(planDayFormDTO,planDay);

        this.updatePlan(planDay,mesBaseLine);

    }

    /**
     * 根据日计划  更新是 产品信息不能修改
     * 产线可以修改
     * 时间可以修改
     * @param planDayFormDTO
     */
    @Override
    @Transactional
    public void updataPlanDay(@Valid PlanDayFormDTO planDayFormDTO) {

        MesProducePlanDay planDay = getById(planDayFormDTO.getId());

        MesBaseLine mesBaseLine = Optional.ofNullable(mesBaseLineService.infoByCode(planDayFormDTO.getLinesCode())).orElseThrow(() -> new BizException("产线中不存在该产线"));
        planDay.setProductCount(planDayFormDTO.getProductCount());
        planDay.setPlanStartDate(planDayFormDTO.getPlanStartDate());
        this.updatePlan(planDay,mesBaseLine);
    }

    /**
     * 结束日工单
     * @param planCode
     */
    @Override
    @Transactional
    public void stop(String planCode) {

        MesProducePlanDay mesProducePlanDay = this.getOne(Wrappers.<MesProducePlanDay>lambdaQuery().eq(MesProducePlanDay::getPlanCode, planCode));
        Optional.ofNullable(mesProducePlanDay).orElseThrow(() -> new BizException("日工单不存在"));
        if(PlanStatusEnum.CLOSE.getType().equals(mesProducePlanDay.getStatusCode())){
            throw new BizException("工单已经结束,不能操作");
        }
        mesProducePlanDay.setStatus(PlanStatusEnum.FORCECLOSE.getDescription());
        mesProducePlanDay.setStatusCode(PlanStatusEnum.FORCECLOSE.getType());
        mesProducePlanDay.setRealityEndDate(LocalDateTime.now());
        this.updateById(mesProducePlanDay);

        // 关闭对应的工作中心计划

        List<WorkCenterPlan> workCenterPlans = workCenterPlanService.list(Wrappers.<WorkCenterPlan>lambdaQuery()
                .eq(WorkCenterPlan::getPlanCode, planCode));

        List<WorkCenterPlan> collect = workCenterPlans.stream().peek(item -> {
            item.setStatusCode(PlanStatusEnum.FORCECLOSE.getType());
            item.setStatus(PlanStatusEnum.FORCECLOSE.getDescription());
            item.setRealityEndDate(mesProducePlanDay.getRealityEndDate());
        }).collect(Collectors.toList());


        // 更新工作中心计划
        workCenterPlanService.updateBatchById(collect);
    }

    /**
     * 工单完成数量
     * @param planCode
     * @param num
     */
    @Override
    public void addSuccsessMat(String planCode, Long num) {
        MesProducePlanDay one = this.getOne(Wrappers.<MesProducePlanDay>lambdaQuery()
                .eq(MesProducePlanDay::getPlanCode, planCode));
        if(one.getStatusCode().equals(PlanStatusEnum.FINISH.getType())){
            throw new BizException("工单已经完成不能报工");
        }
        one.setSuccessCount(one.getSuccessCount() + num);
        if(one.getSuccessCount() >= one.getProductCount()){
            one.setStatus(PlanStatusEnum.FINISH.getDescription());
            one.setStatusCode(PlanStatusEnum.FINISH.getType());
        }
        this.updateById(one);
    }

    /**
     * 按照工单进行齐套检查
     * @param planCode
     * @return
     */
    @Override
    public List<MatCheckVO> check(String planCode) {
        MesProducePlanDay one = Optional.ofNullable(this.getOne(Wrappers.<MesProducePlanDay>lambdaQuery()
                .eq(MesProducePlanDay::getPlanCode, planCode))
        ).orElseThrow(() -> new BizException("工单不存在"));

        List<MesBaseBom> list = mesBaseBomService.list(one.getProductCode());

        return this.check(one.getProductCode(),one.getProductCount(),list);

    }


    /**
     * 齐套检查
     * 齐套检查在没有仓库系统时指挥检查线边库对应的物料
     * 如果对接仓库系统则会加上仓库系统的余量进行检查
     * 如果线边需要限定库位时只会检查对应库位的物料(可用物料)
     * @param matCode
     * @param num
     * @return
     */
    @Override
    public List<MatCheckVO> check(String matCode, Long num,List<MesBaseBom> bomList) {

        // 默认先去对应线边的仓库的数量
        List<String> collect = bomList.stream().map(MesBaseBom::getMatCode).collect(Collectors.toList());
        List<MaterialReportVO> materialReportVOS = mesSideWarehouseService.reportMatNum(collect);
        List<MatCheckVO> collect1 = bomList.stream().map(item -> {
            MatCheckVO matCheckVO = new MatCheckVO();
            matCheckVO.setMatCode(item.getMatCode());
            matCheckVO.setMatName(item.getMatName());

            List<MaterialReportVO> materialReportVOList = materialReportVOS.stream()
                    .filter(it -> it.getMatCode().equals(item.getMatCode()))
                    .collect(Collectors.toList());

            if (CollUtil.isNotEmpty(materialReportVOList)) {
                matCheckVO.setSurplusNum(materialReportVOList.get(0).getNum());
            } else {
                matCheckVO.setSurplusNum(0f);
            }
            matCheckVO.setNum(num * item.getCount());
            return matCheckVO;
        }).collect(Collectors.toList());

        return collect1;
    }

    /**
     *
     * @param startTime
     * @param endTime
     */
    @Override
    public List<PlanDayGanttVO> linesPlan(LocalDateTime startTime, LocalDateTime endTime) {
        // 查询所有的计划
        List<MesProducePlanDay> list = this.list(Wrappers.<MesProducePlanDay>lambdaQuery()
                .ge(MesProducePlanDay::getPlanStartDate, startTime)
//                .le(MesProducePlanDay::getPlanEndDate, endTime)
                .in(MesProducePlanDay::getStatusCode, PlanStatusEnum.WAITING.getType(), PlanStatusEnum.GOING.getType())
        );
        // 列出所有的 产线 ， 按照产线进行 统计信息

        Map<String, List<MesProducePlanDay>> collect = list.stream().collect(Collectors.groupingBy(MesProducePlanDay::getLinesCode));

        List<PlanDayGanttVO> dayGanttVOList = new ArrayList<>();
        for (String lineCode : collect.keySet()) {
            PlanDayGanttVO vo = new PlanDayGanttVO();
//            vo.setParent(lineCode);

            List<MesProducePlanDay> mesProducePlanDays = collect.get(lineCode);
            MesProducePlanDay planDay = mesProducePlanDays.get(0);

            vo.setLinesName(planDay.getLinesName());
            vo.setLinesCode(planDay.getLinesCode());
            vo.setPlanCode(lineCode);


            LocalDateTime lineStartTime = planDay.getPlanStartDate();
            LocalDateTime lineEndTime = planDay.getPlanEndDate();
            // 计划数量
            Long num = 0L;
            for (MesProducePlanDay item: mesProducePlanDays){
                PlanDayGanttVO planDayGanttVO = new PlanDayGanttVO();
                BeanUtil.copyProperties(item, planDayGanttVO);
                planDayGanttVO.setParent(lineCode);
                planDayGanttVO.setIsParent("N");
                // 更新开始时间
                if (lineStartTime.isAfter(item.getPlanStartDate())) {
                    lineStartTime = item.getPlanStartDate();
                }

                if (lineEndTime.isBefore(item.getPlanEndDate())) {
                    lineEndTime = item.getPlanEndDate();
                }
                dayGanttVOList.add(planDayGanttVO);
                num += item.getProductCount();
            }
            //
            vo.setPlanStartDate(lineStartTime);
            //
            vo.setPlanEndDate(lineEndTime);
            vo.setProductCount(num);
            vo.setSuccessCount(0L);
            vo.setReadonly("Y");
            vo.setIsParent("Y");
            dayGanttVOList.add(vo);
        }

        return dayGanttVOList;


    }


    /**
     * 更新日工单信息
     * @param planDay
     * @param mesBaseLine
     */
    private void updatePlan(MesProducePlanDay planDay,MesBaseLine mesBaseLine){

        planDay.setLinesCode(planDay.getLinesCode());
        planDay.setLinesName(mesBaseLine.getLineName());
        // 设定状态
        planDay.setStatus(PlanStatusEnum.WAITING.getDescription());
        planDay.setStatusCode(PlanStatusEnum.WAITING.getType());

        planDay.setProductSpeed(mesBaseLine.getCapacity());
        planDay.setSuccessCount(0L);
        planDay.setBanci(MesUtils.calcBanciByDate(planDay.getPlanStartDate()));
        planDay.setPlanCode(MesUtils.getMesCode(MesCodeEnums.PLANCODE));
        LocalDateTime planEndTime = MesUtils.calcTimeByProductSpeed(planDay.getPlanStartDate(), mesBaseLine.getCapacity(), planDay.getProductCount());
        planDay.setPlanEndDate(planEndTime);

        // 根据产能计划对应的时间
        this.saveOrUpdate(planDay);
    }
}
